<?php
if(isset($GLOBALS['context']))	require_lib('openapi/OpenAPIRequest',false);
else	require_once dirname(__FILE__).'/OpenAPIRequest.class.php';

/** 
 * json rpc,通过http协议调用web函数，得到相应json返回值。
 */
class JsonRpcRequest extends OpenAPIRequest{
	/**
	 * @var string $host_url jsonrpc的网站地址
	 */	
	var $host_url;
	/**
	 * @var string 请求语言，支持en,zh_cn等，默认zh_cn
	 */
	var $lang;
	/**
	 * @var array $app_token 应用app访问JsonRpc的令牌，参数包括：<br/>
	 * string app_key : 必须，  API应用的标志符.<br/>
	 * string app_secret :可选，默认为APP_SALT，用于函数参数鉴权，将相关参数通过md5,hmac加密为一个sign值<br/>
	 */
	var $app_token=array();
	var $last_sign;
	
	/**
	 * @var array $app_setting 应用app访问taobao的通用参数设置，一般不改变默认值，包括：<br/>
	 * strng  app_sign_method :可选，签名方法，md5,hmac，默认是 'md5'。<br/>
	 */
	var $app_setting=array();//'app_sign_method'=>'md5'
	
    function __construct($host_url,$app_key,$app_secret=NULL){
		parent::__construct();
    	$this->host_url=$host_url;
    	$this->app_token['app_key']=$app_key;
    	if($app_secret)	$this->app_token['app_secret']=$app_secret;
    }

    
	/**
	 * 得到json API的返回值，JSON字符串。
	 * @param string $cmdid 要调用的api接口的app_act字符串，如orderinfo/product_list，如果为空，将返回false.
	 * @param array $params 要调用的api接口的参数数组，一般通用的参数不要传，如app_mode等。
	 * @return string|bool 调用的api接口得到的JSON字符串，失败将抛出异常。
	 */
	function &get_resp($cmdid,array $params){
		if(! $cmdid) return false;
		if(! $this->app_token['app_key'])  return false;
		if(! $this->app_token['app_secret'])   $this->app_token['app_secret']=APP_SALT;  
		
		//组装url,param array.
		$this->last_sign=$sign=$this->_createSign($this->app_token['app_secret'],$params,$this->app_token['app_key'],
				isset($this->app_setting['app_sign_method'])?$this->app_setting['app_sign_method']:'md5');
		$params=$params + $this->app_setting;
		$params['app_key']=$this->app_token['app_key'];
		$params['app_act']=$cmdid;
		$params['app_sign']=$sign;
		$params['app_mode']='func';
		if(isset($this->lang)) $params['app_lang']=$this->lang;
		
		$result=& $this->send($this->host_url,$params);
		return $result;
	}
	
	/**
	 * 得到 API的返回值，JSON生成的数组。
	 * @param string $cmdid 要调用的api接口的app_act字符串，如果为空，将返回false.
	 * @param array $params 要调用的api接口的参数数组，一般通用的参数不要传，如app_mode等。
	 * @param string $resp_handle 用于额外处理get_resp函数返回字符串的函数名称，此函数调用在调用get_resp函数后，解析为数组前，默认为NULL不调用。
	 * @return array|bool 调用的api接口得到的 JSON,XML生成的数组，API返回错误返回false，失败将抛出异常。
	 */	
	protected function &get_array($cmdid,array $params,$resp_handle=NULL){
		//parse json format
		if(!($result=&$this->get_resp($cmdid,$params))) return false;
		
		if($resp_handle)  //addition handle result 
			try{$result = $resp_handle($result);}catch (Exception  $e){}
		$result = preg_replace('/(":)(\d{9,})/i', '${1}"${2}"', $result); //while number >2^32
		
		$arr=json_decode($result,true);
		if ($arr == NULL) { //normal kick invalid json
			$patterns = array ('/,+\s*\}/', '/,+\s*\]/', '/"\s+|\s+"/','/\n|\r/' );
			$replacements = array ('}', ']', '"',' ' );
			$result = preg_replace ( $patterns, $replacements, $result );
			$arr = json_decode ( $result, true );
		}
		if (! $this->check_result ( $arr, $this->err_no, $this->err_msg )){
			$msg = $this->err_msg . ";\n{$cmdid}";
			if(isset($GLOBALS['context'])) $GLOBALS['context']->log_error($msg);
			$f=false; return $f;
		}		
		if(is_array($arr) && isset($arr['resp_data'])) $arr=$arr['resp_data'];
		return $arr;
	}	

	/**
	 * 检查接口调用get*函数返回是否有错误
	 * @param  mixed $result  接口调用get*函数返回值.
	 * @return bool true 接口调用有效，false 接口调用出错.
	 */
	protected function check_result(&$result,&$errorCode,&$errorMsg){
		$errorCode=0;
		$errorMsg=''; 		
		if(is_array($result)){
			if(! array_key_exists('resp_error',$result))
			    return true;
			else{
				$error=$result['resp_error'];
				$errorCode=$error['app_err_no'];
				$errorMsg=$error['app_err_msg'];
			}
		}elseif (is_string($result)){
			$match=array();
			if(! preg_match('/^\{"resp_error":\{.+,"app_err_no":(\d*),"app_err_msg":"(.+)\}\}$/',$result,$match))
			     return true;
			else{
				$errorCode=$match[1];
				$errorMsg=rtrim($match[2],'"');
				
			}
		}
		elseif(is_bool($result)) return $result;
		return false;
	}
	/*
	 * 签名函数,内部用 
	 */
    private function _createSign($app_secret,array & $paramArr,$prefix='',$sign_method='md5') {
		$sign=& $this->_to_sign_str($paramArr);
		if($sign_method==='hmac')
			return strtoupper(hash_hmac('md5',$prefix . $sign,$app_secret));
		else
			return strtoupper(md5($app_secret. $prefix . $sign . $app_secret));
	}	
}

